#ifndef _WRITER_HPP_
#define _WRITER_HPP_

#include "json/jsoncpp/value.hpp"
#include <vector>
#include <string>
#include <ostream>

namespace JHCPP
{
	namespace json
	{
		namespace jsoncpp
		{
			class Value;

			/**
			Usage:
			\code
			using namespace Json;
			void writeToStdout(StreamWriter::Factory const& factory, Value const& value) {
			std::unique_ptr<StreamWriter> const writer(
			factory.newStreamWriter());
			writer->write(value, &std::cout);
			std::cout << std::endl;  // add lf and flush
			}
			\endcode
			*/
			class StreamWriter 
			{
			protected:
				std::ostream* m_sout;  // not owned; will not delete
			public:
				StreamWriter();
				virtual ~StreamWriter();
				/** Write Value into document as configured in sub-class.
				Do not take ownership of sout, but maintain a reference during function.
				\pre sout != NULL
				\return zero on success (For now, we always return zero, so check the stream instead.)
				\throw std::exception possibly, depending on configuration
				*/
				virtual int write(Value const& root, std::ostream* sout) = 0;

				/** \brief A simple abstract factory.
				*/
				class Factory 
				{
				public:
					virtual ~Factory();
					/** \brief Allocate a CharReader via operator new().
					* \throw std::exception if something goes wrong (e.g. invalid settings)
					*/
					virtual StreamWriter* newStreamWriter() const = 0;
				};  // Factory
			};  // StreamWriter

			/** \brief Write into stringstream, then return string, for convenience.
			* A StreamWriter will be created from the factory, used, and then deleted.
			*/
			std::string writeString(StreamWriter::Factory const& factory, Value const& root);


			/** \brief Build a StreamWriter implementation.

			Usage:
			\code
			using namespace Json;
			Value value = ...;
			StreamWriterBuilder builder;
			builder["commentStyle"] = "None";
			builder["indentation"] = "   ";  // or whatever you like
			std::unique_ptr<Json::StreamWriter> writer(
			builder.newStreamWriter());
			writer->write(value, &std::cout);
			std::cout << std::endl;  // add lf and flush
			\endcode
			*/
			class StreamWriterBuilder : public StreamWriter::Factory
			{
			public:
				// Note: We use a Json::Value so that we can add data-members to this class
				// without a major version bump.
				/** Configuration of this builder.
				Available settings (case-sensitive):
				- "commentStyle": "None" or "All"
				- "indentation":  "<anything>"
				- "enableYAMLCompatibility": false or true
				- slightly change the whitespace around colons
				- "dropNullPlaceholders": false or true
				- Drop the "null" string from the writer's output for nullValues.
				Strictly speaking, this is not valid JSON. But when the output is being
				fed to a browser's Javascript, it makes for smaller output and the
				browser can handle the output just fine.

				You can examine 'settings_` yourself
				to see the defaults. You can also write and read them just like any
				JSON Value.
				\sa setDefaults()
				*/
				Value m_settings;

				StreamWriterBuilder();
				virtual ~StreamWriterBuilder();

				/**
				* \throw std::exception if something goes wrong (e.g. invalid settings)
				*/
				virtual StreamWriter* newStreamWriter() const;

				/** \return true if 'settings' are legal and consistent;
				*   otherwise, indicate bad settings via 'invalid'.
				*/
				bool validate(Value* invalid) const;
				/** A simple way to update a specific setting.
				*/
				Value& operator[](std::string key);

				/** Called by ctor, but you can use this to reset settings_.
				* \pre 'settings' != NULL (but Json::null is fine)
				* \remark Defaults:
				* \snippet src/lib_json/json_writer.cpp StreamWriterBuilderDefaults
				*/
				static void setDefaults(Value* settings);
			};

			/** \brief Abstract class for writers.
			* \deprecated Use StreamWriter. (And really, this is an implementation detail.)
			*/
			class Writer 
			{
			public:
				virtual ~Writer();

				virtual std::string write(const Value& root) = 0;
			};

			/** \brief Outputs a Value in <a HREF="http://www.json.org">JSON</a> format
			*without formatting (not human friendly).
			*
			* The JSON document is written in a single line. It is not intended for 'human'
			*consumption,
			* but may be usefull to support feature such as RPC where bandwith is limited.
			* \sa Reader, Value
			* \deprecated Use StreamWriterBuilder.
			*/
			class FastWriter : public Writer 
			{
			public:
				FastWriter();
				virtual ~FastWriter() {}

				void enableYAMLCompatibility();

				/** \brief Drop the "null" string from the writer's output for nullValues.
				* Strictly speaking, this is not valid JSON. But when the output is being
				* fed to a browser's Javascript, it makes for smaller output and the
				* browser can handle the output just fine.
				*/
				void dropNullPlaceholders();

				void omitEndingLineFeed();

			public: // overridden from Writer
				virtual std::string write(const Value& root);

			private:
				void writeValue(const Value& value);

				std::string m_document;
				bool m_yamlCompatiblityEnabled;
				bool m_dropNullPlaceholders;
				bool m_omitEndingLineFeed;
			};

			/** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> format in a
			*human friendly way.
			*
			* The rules for line break and indent are as follow:
			* - Object value:
			*     - if empty then print {} without indent and line break
			*     - if not empty the print '{', line break & indent, print one value per
			*line
			*       and then unindent and line break and print '}'.
			* - Array value:
			*     - if empty then print [] without indent and line break
			*     - if the array contains no object value, empty array or some other value
			*types,
			*       and all the values fit on one lines, then print the array on a single
			*line.
			*     - otherwise, it the values do not fit on one line, or the array contains
			*       object or non empty array, then print one value per line.
			*
			* If the Value have comments then they are outputed according to their
			*#CommentPlacement.
			*
			* \sa Reader, Value, Value::setComment()
			* \deprecated Use StreamWriterBuilder.
			*/
			class StyledWriter : public Writer 
			{
			public:
				StyledWriter();
				virtual ~StyledWriter() {}

			public: // overridden from Writer
				/** \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format.
				* \param root Value to serialize.
				* \return String containing the JSON document that represents the root value.
				*/
				virtual std::string write(const Value& root);

			private:
				void writeValue(const Value& value);
				void writeArrayValue(const Value& value);
				bool isMultineArray(const Value& value);
				void pushValue(const std::string& value);
				void writeIndent();
				void writeWithIndent(const std::string& value);
				void indent();
				void unindent();
				void writeCommentBeforeValue(const Value& root);
				void writeCommentAfterValueOnSameLine(const Value& root);
				bool hasCommentForValue(const Value& value);
				static std::string normalizeEOL(const std::string& text);

				typedef std::vector<std::string> ChildValues;

				ChildValues m_childValues;
				std::string m_document;
				std::string m_indentString;
				int m_rightMargin;
				int m_indentSize;
				bool m_addChildValues;
			};

			/** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> format in a
			human friendly way,
			to a stream rather than to a string.
			*
			* The rules for line break and indent are as follow:
			* - Object value:
			*     - if empty then print {} without indent and line break
			*     - if not empty the print '{', line break & indent, print one value per
			line
			*       and then unindent and line break and print '}'.
			* - Array value:
			*     - if empty then print [] without indent and line break
			*     - if the array contains no object value, empty array or some other value
			types,
			*       and all the values fit on one lines, then print the array on a single
			line.
			*     - otherwise, it the values do not fit on one line, or the array contains
			*       object or non empty array, then print one value per line.
			*
			* If the Value have comments then they are outputed according to their
			#CommentPlacement.
			*
			* \param indentation Each level will be indented by this amount extra.
			* \sa Reader, Value, Value::setComment()
			* \deprecated Use StreamWriterBuilder.
			*/
			class StyledStreamWriter
			{
			public:
				StyledStreamWriter(std::string indentation = "\t");
				~StyledStreamWriter() {}

			public:
				/** \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format.
				* \param out Stream to write to. (Can be ostringstream, e.g.)
				* \param root Value to serialize.
				* \note There is no point in deriving from Writer, since write() should not
				* return a value.
				*/
				void write(std::ostream& out, const Value& root);

			private:
				void writeValue(const Value& value);
				void writeArrayValue(const Value& value);
				bool isMultineArray(const Value& value);
				void pushValue(const std::string& value);
				void writeIndent();
				void writeWithIndent(const std::string& value);
				void indent();
				void unindent();
				void writeCommentBeforeValue(const Value& root);
				void writeCommentAfterValueOnSameLine(const Value& root);
				bool hasCommentForValue(const Value& value);
				static std::string normalizeEOL(const std::string& text);

				typedef std::vector<std::string> ChildValues;

				ChildValues m_childValues;
				std::ostream* m_document;
				std::string m_indentString;
				int m_rightMargin;
				std::string m_indentation;
				bool m_addChildValues : 1;
				bool m_indented : 1;
			};

#if defined(JSON_HAS_INT64)
			std::string valueToString(Int value);
			std::string valueToString(UInt value);
#endif // if defined(JSON_HAS_INT64)
			std::string valueToString(LargestInt value);
			std::string valueToString(LargestUInt value);
			std::string valueToString(double value);
			std::string valueToString(bool value);
			std::string valueToQuotedString(const char* value);

			/// \brief Output using the StyledStreamWriter.
			/// \see Json::operator>>()
			std::ostream& operator<<(std::ostream&, const Value& root);
		}//end namespace jsoncpp
	}//end namespace json
}//end namespace JHCPP

#endif
